写个转盘游戏,被薅羊毛了~
在 Android 开发者选项中,是可以将“动画程序时长缩放”,设置为 0,这样就可以跳过大部分需要动画的场景,直接出结果。
这个功能在我们调试动画效果的时候非常好用,但是对于已经发布的项目,如果业务严重依赖开始和结束的动画效果,这就明显可以绕过动画时长的限制,无论怎么说也是一种漏洞的存在。
举个例子,猎豹清理大师国际版中,就有一个转盘游戏,可以通过此游戏领取积分,积分积累到达一定数额之后是可以直接提现的。
假如这里可以跳过动画,等于用户可以一直点一直点,这肯定不是正确的产品形态。
ValueAnimator
这里就用 ValueAnimator 来说明,当你在开发者选项中,修改了动画参数之后,其实影响的是 ValueAnimator 中的 sDurationScale
值。
/**
* Internal constants
*/
private static float sDurationScale = 1.0f;
这在默认情况下,它的值为 1.0f
,在开发者选项中的修改,直接反映在这个值上。
public final boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
}
// ...
// The frame time might be before the start time during the first frame of
// an animation. The "current time" must always be on or after the start
// time to avoid animating frames at negative time intervals. In practice, this
// is very rare and only happens when seeking backwards.
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
}
从 doAnimationFrame()
方法中可以看到,其中会根据 animateBasedOnTime()
方法的返回值来勘定当前动画是否结束,如果结束则调用 endAnimation()
。
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
// ...
if (scaledDuration == 0) {
// 0 duration animator, ignore the repeat count and skip to the end
done = true;
} // ...
// ...
}
return done;
}
从 animateBasedOnTime()
方法中可以看出,只要 getScaledDuration()
返回 0,则表示当前动画已经结束。
private long getScaledDuration() {
return (long)(mDuration * sDurationScale);
}
这里,只要 sDurationScale
等于 0,任何时候 getScaledDuration()
都会返回 0。
这样就做到了跳过动画的操作。
通过反射解决问题
既然已经知道其中的原理,所以我们只需要想办法保证这个值一直为 1,就好了。
可惜 sDurationScale
是私有的,我们只能通过反射去修改它。
public class AnimatorSettingUtils {
public static boolean tryOpenAnimator() {
try {
Field field = ValueAnimator.class.getDeclaredField("sDurationScale");
field.setAccessible(true);
if (field.getFloat(null) != 1) {
field.setFloat(null, 1);
}
} catch (Exception ignore) {
}
return true;
}
}
此时,在检测到 sDurationScale
的值不为 1 的时候,就将它修改为 1。
到这里就算解决问题了,如果你仔细看过源码,ValueAnimator 中还有一个 areAnimatorsEnabled()
方法,这个 API 是在 Level 26(Android 8.0) 中被加入的,用来判定当前动画是否被开启。
从文档中描述,此方法有两种情况下会返回 false,将动画持续时间设置为 0,或者启动了"省电模式"(并且设置了省点模式下禁用所有动画),则此值会一直返回 false。所以其实并非用户手动设置关闭动画,还有可能在省电模式下自动关闭动画。
因此,如果我们的业务都是依赖动画的执行和结束,我们都需要注意动画是可能提前结束的。在做这样需求的时候,思考一下这样的场景,是否符合我们的功能需要。
END
本文对你有帮助吗?留言、点赞、转发是最大的支持,谢谢!
「联机圆桌」👈推荐我的知识星球,一年 50 个优质问题,上桌联机学习。
公众号后台回复成长『成长』,将会得到我准备的学习资料,也能回复『加群』,一起学习进步;你还能回复『提问』,向我发起提问。
推荐阅读:
“寒冬”正是学习时|关于字符编码,你需要知道的都在这里 | 分词,科普及解决方案| 图解:HTTP 范围请求 | 小程序学习资料 |HTTP 内容编码 | 辅助模式实战 | 辅助模式玩出花样 | 小程序 Flex 布局